'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with self work for additional information
 * regarding copyright ownership.  The ASF licenses self file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use self file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:#www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''
from abc import ABC, abstractmethod

from base.graph import Node_ANY
from base.graph.Node import Node
from base.java.Objects import Objects
from base.java.exception.IllegalStateException import IllegalStateException
from base.shared.PrefixMapping import PrefixMapping

#import java.io.IOException;
#import java.io.ObjectStreamException;
#import java.io.Serializable;
#import java.util.function.Function;
#import java.util.function.Predicate;

#import org.apache.jena.shared.PrefixMapping;
#import org.apache.jena.sys.Serializer;

'''
 * Triples are the basis for RDF statements they have a subject, predicate, and
 * object field (all nodes) and express the notion that the relationship named
 * by the predicate holds between the subject and the object.
'''


class Triple:

    '''
     * @deprecated Do not call "new Triple" directly, use:@link Triple#create.
    '''
    #@Deprecated
    # param Node s, Node p, Node o:
    def __init__(self, s, p, o):
        Objects.requireNonNull(s, 'subject can not be null')
        Objects.requireNonNull(s, 'predicate can not be null')
        Objects.requireNonNull(s, 'object can not be null')
        self.subj = s
        self.pred = p
        self.obj = o

    '''
     * return a human-readable string "subject @predicate object" describing the triple
    '''

    #@Override
    # param 
    # result String
    # def toString(self):
    #     return toString(PrefixMapping.Standard)

    # param PrefixMapping pm
    # result String
    def toString(self):
        return self.subj.toString() + " @" + self.pred.toString() + " " + self.obj.toString()

    '''
     * @return the subject of the triple
    '''

    # param
    # result final Node
    def getSubject(self):
        return self.subj

    '''
     * @return the predicate of the triple
    '''

    # param
    # result final Node
    def getPredicate(self):
        return self.pred

    '''
     * @return the object of the triple
    '''

    # param
    # result final Node
    def getObject(self):
        return self.obj

    '''
     * Return subject or null, not Node_ANY.ANY
    '''

    # param
    # result Node
    def getMatchSubject(self):
        return self.anyToNull(self.subj)

    '''
     * Return predicate or null, not Node_ANY.ANY
    '''

    # param
    # result Node
    def getMatchPredicate(self):
        return self.anyToNull(self.pred)

    '''
     * Return object or null, not Node_ANY.ANY
    '''

    # param
    # result Node
    def getMatchObject(self):
        return self.anyToNull(self.obj)

    # param Node n
    # result static Node
    def anyToNull(self, n):
        return None if Node_ANY.ANY.equals(n) else n

    # param Node n
    # result static Node
    def nullToAny(self, n):
        return Node_ANY.ANY if n is None else n

    # param Node n
    # result static boolean
    @staticmethod
    def isAny(n):
        return n is None or Node_ANY.ANY.equals(n)

    # param 
    # result boolean
    def isConcrete(self):
        return self.subj.isConcrete() and self.pred.isConcrete() and self.obj.isConcrete()

    '''
     * Answer True if <code>o</code> is a Triple with the same subject, predicate,
     * and object as self triple.
    '''

    #@Override
    # param Object o
    # result boolean
    def equals(self, o):
        #return o instanceof Triple and ((Triple) o).sameAs(self.subj, self.pred, self.obj)
        return isinstance(o, Triple) and o.sameAs(self.subj, self.pred, self.obj)

    '''
     * Answer True iff self triple has subject s, predicate p, and object o.
    '''

    # param Node s, Node p, Node o
    # result boolean
    def sameAs(self, s, p, o):
        return self.subj.equals(s) and self.pred.equals(p) and self.obj.equals(o)

    '''
     * Does self triple, used as a pattern match, the other triple (usually a ground triple)
    '''

    # param Triple other
    # result boolean
    def matches(self, other):
        return other.matchedBy(self.subj, self.pred, self.obj)

    # param Node s, Node p, Node o
    # result boolean
    def matchesSPO(self, s, p, o):
        return self.subj.matches(s) and self.pred.matches(p) and self.obj.matches(o)

    # param Node s, Node p, Node o
    # result boolean
    def matchedBy(self, s, p, o):
        return s.matches(self.subj) and p.matches(self.pred) and o.matches(self.obj)

    # param Node s
    # result boolean
    def subjectMatches(self, s):
        return self.subj.matches(s)

    # param Node p
    # result boolean
    def predicateMatches(self, p):
        return self.pred.matches(p)

    # param Node o
    # result boolean
    def objectMatches(self, o):
        return self.obj.matches(o)

    # ---- Serializable
    # param 
    # result Object
    # def writeReplace(self):
    #     Function<Triple, Object> function = Serializer.getTripleSerializer()
    #     if (function is None)
    #         raise IllegalStateException("Function for Triple.writeReplace not set")
    #     return function.apply(self)

    # Any attempt to serialize without replacement is an error.
    # param java.io.ObjectOutputStream out
    # result void
    def writeObject(self, out):
        raise IllegalStateException('Any attempt to serialize without replacement is an error.')

    # param java.io.ObjectInputStream in
    # result void
    def readObject(self, input):
        raise IllegalStateException('Any attempt to serialize without replacement is an error.')

    # ---- Serializable

    '''
     * The hash-code of a triple is the hash-codes of its components munged
     * together: see hashCode(S, P, O).
    '''

    #@Override
    # param 
    # result int
    def hashCode(self):
        return self.hashCodeSPO(self.subj, self.pred, self.obj)

    '''
     * Return the munged hashCodes of the specified nodes, an exclusive-or of
     * the slightly-shifted component hashcodes self means (almost) all of the bits
     * count, and the order matters, so (S @P O) has a different hash from
     * (O @P S), etc.
    '''

    # param Node s, Node p, Node o
    # result static int
    def hashCodeSPO(self, s, p, o):
        return (s.hashCode() >> 1) ^ p.hashCode() ^ (o.hashCode() << 1)

    # param Node s, Node p, Node o
    # result static Triple
    @staticmethod
    def create(s, p, o):
        if (Triple.isAny(s) and Triple.isAny(p) and Triple.isAny(o)):
            return ANY
        return Triple(s, p, o)

    # param Node s, Node p, Node o
    # result static Triple
    def createMatch(self, s, p, o):
        return Triple.create(self.nullToAny(s), self.nullToAny(p), self.nullToAny(o))

    class Field(ABC):
        def __init__(self):
            pass

        @abstractmethod
        def getField(self, t):
            pass

        @abstractmethod
        def filterOn(self, n):
            pass

        @abstractmethod
        def filterOnConcrete(self, t):
            pass

        def filterOnTriple(self, t):
            return self.filterOn(self.getField(t))

        anyTriple = lambda: True


class FieldSubject(Triple.Field):
    def getField(self, t):
        return t.subj

    # param Node
    # result lambda
    def filterOn(self, n):
        return lambda x: n.equals(x.subj) if n.isConcrete() else Triple.Field.anyTriple

    # param Node
    # result lambda
    def filterOnConcrete(self, n):
        return lambda x: n.equals(x.subj)


class FieldPredicate(Triple.Field):
    def getField(self, t):
        return t.pred

    # param Node
    # result lambda
    def filterOn(self, n):
        return lambda x: n.equals(x.pred) if n.isConcrete() else Triple.Field.anyTriple

    # param Node
    # result lambda
    def filterOnConcrete(self, n):
        return lambda x: n.equals(x.pred)


class FieldObject(Triple.Field):
    def getField(self, t):
        return t.obj

    # param Node
    # result lambda
    def filterOn(self, n):
        return lambda x: n.equals(x.obj) if n.isConcrete() else Triple.Field.anyTriple

    # param Node
    # result lambda
    def filterOnConcrete(self, n):
        return lambda x: n.equals(x.obj)


'''
     * A Triple that has:@link Node#ANY in all fields.
    '''
# param Node_ANY.ANY, Node_ANY.ANY, Node_ANY.ANY
# result static final Triple ANY = new
ANY = Triple(Node_ANY.ANY, Node_ANY.ANY, Node_ANY.ANY)

Triple.Field.fieldSubject = FieldSubject()
Triple.Field.fieldPredicate = FieldPredicate()
Triple.Field.fieldObject = FieldObject()
